package com.xgimi.open.skill.datamanager.biz.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.xgimi.gmui.glog.sdk.GlogRecorder;
import com.xgimi.open.skill.datamanager.biz.common.Enum.ExceptionErrorCode;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizAssert;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizException;
import com.xgimi.open.skill.datamanager.biz.common.redis.RedisLockService;
import com.xgimi.open.skill.datamanager.biz.constant.NluConstants;
import com.xgimi.open.skill.datamanager.biz.service.SkillBizService;
import com.xgimi.open.skill.datamanager.biz.utils.BusinessIdUtil;
import com.xgimi.open.skill.datamanager.biz.utils.RedisKeyBuilder;
import com.xgimi.open.skill.datamanager.domain.entity.IntentDO;
import com.xgimi.open.skill.datamanager.domain.entity.SkillDO;
import com.xgimi.open.skill.datamanager.domain.entity.SkillVersionDO;
import com.xgimi.open.skill.datamanager.domain.service.*;
import com.xgimi.open.skill.datamanager.service.request.SkillReqDTO;
import com.xgimi.open.skill.datamanager.service.response.SkillRespDTO;
import com.xgimi.open.skill.datamanager.service.response.page.IPageUtils;
import com.xgimi.open.skill.datamanager.service.response.page.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author coderzpw.zhang
 * @version 1.0
 * @description 描述
 * @date 2023/2/7 19:36
 */
@Slf4j
@Service
public class SkillBizServiceImpl implements SkillBizService {
    public static final Integer IS_TOP = 1;
    public static final Integer IS_NOT_TOP = 0;
    public static final Integer UN_PUBLISHED = 0;
    public static final String ONLINE = "online";
    public static final String TEST = "test";
    private final SkillService skillService;
    private final SkillVersionService skillVersionService;
    private final WordStockService wordStockService;
    private final SlotService slotService;
    private final IntentService intentService;
    private final SpeechRuleService speechRuleService;
    private final RedisLockService redisLockService;

    private final GlogRecorder glogRecorder;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public SkillBizServiceImpl(SkillService skillService,
                               SkillVersionService skillVersionService,
                               WordStockService wordStockService,
                               SlotService slotService,
                               IntentService intentService,
                               SpeechRuleService speechRuleService,
                               RedisLockService redisLockService,
                               GlogRecorder glogRecorder) {
        this.skillService = skillService;
        this.skillVersionService = skillVersionService;
        this.wordStockService = wordStockService;
        this.slotService = slotService;
        this.intentService = intentService;
        this.speechRuleService = speechRuleService;
        this.redisLockService = redisLockService;
        this.glogRecorder = glogRecorder;
    }

    @Transactional
    @Override
    public Boolean addSkill(SkillReqDTO skillReqDTO) {
        SkillDO skillDO = new SkillDO();
        BeanUtils.copyProperties(skillReqDTO, skillDO);
        skillDO.setCreatedBy(skillReqDTO.getCreatedBy());
        // 1、查询表中是否已有重复的skill，根据skillName判断
        if (StringUtils.isEmpty(skillDO.getSkillName())) {
            return Boolean.FALSE;
        }
        List<SkillDO> list = skillService.querySkillByName(skillDO);
        if (list.size() > 0) {
            throw new BizException(ExceptionErrorCode.SKILL_NAME_ALREADY_EXISTS);
        }
        // 2、新增skill
        skillDO.setBusinessId(BusinessIdUtil.getSkillUUID());
        Boolean ret = skillService.saveSkill(skillDO);
        // 3、添加一个 默认的skillVersion（测试版本）
        if (ret) {
            SkillVersionDO skillVersionDO = new SkillVersionDO();
            skillVersionDO.setBusinessId(BusinessIdUtil.getSkillVersionUUID());
            skillVersionDO.setSkillId(skillDO.getId());
            skillVersionDO.setVersion(0L);
            skillVersionDO.setStatus(UN_PUBLISHED);
            Boolean isSaveVersion = skillVersionService.saveSkillVersion(skillVersionDO);
        }
        glogRecorder.recordAdd(skillReqDTO.getCreatedBy(),
                "skill", "addSkill",
                ret.toString(), skillReqDTO);
        return ret;
    }

    @Override
    public PageResult<SkillRespDTO> pageQuery(SkillReqDTO skillReqDTO) {
        SkillDO skillDO = new SkillDO();
        BeanUtils.copyProperties(skillReqDTO, skillDO);
        // 构造分页查询的入参
        IPage<SkillDO> page = new Page<>(skillReqDTO.getCurrentPage(), skillReqDTO.getPageSize());
        // 调用domain层分页查询的方法
        IPage<SkillDO> iPage = skillService.pageQuery(page, skillDO);
        // 若查询没结果，则返回空pageResult
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
            log.info("no such skill!");
            return new PageResult<>(Lists.newArrayList(), IPageUtils.noDataPaginator(skillReqDTO.getPageSize()));
        }
        // 若有结果，则将 DO 转成 DTO
        Map<Long, Long> skillTestVidMap = getSkillTestVidMap(iPage.getRecords());
        List<SkillRespDTO> list = iPage.getRecords().stream().map(d -> {
            SkillRespDTO dto = new SkillRespDTO();
            BeanUtils.copyProperties(d, dto);
            dto.setTestVersionId(skillTestVidMap.get(d.getId()));
            return dto;
        }).collect(Collectors.toList());
        return new PageResult<>(list, IPageUtils.transformToPaginator(iPage));
    }

    /**
     * 构造 skillId：testVersionId的映射关系
     */
    private Map<Long, Long> getSkillTestVidMap(List<SkillDO> list) {
        HashMap<Long, Long> ret = new HashMap<>();
        List<Long> ids = list.stream().map(SkillDO::getId).collect(Collectors.toList());
        List<SkillVersionDO> skillVersionDOS = skillVersionService.queryTestVersionsByIds(ids);
        Map<Long, List<SkillVersionDO>> skillSkillVersionMap = skillVersionDOS.stream().collect(Collectors.groupingBy(SkillVersionDO::getSkillId));
        skillSkillVersionMap.forEach((k, v) -> {
            if (CollectionUtils.isEmpty(v)) {
                log.error("技能{}的测试版本缺失", k);
            }
            ret.put(k, v.get(0).getId());
        });
        return ret;
    }

    @Override
    public Boolean updateSkill(SkillReqDTO skillReqDTO) {
        BizAssert.notNull(skillReqDTO.getId(), ExceptionErrorCode.SKILL_ID_IS_NULL);
        boolean ret;
        // 技能写锁
        String skillLockKey = skillLock(skillReqDTO.getBusinessId());
        // 先根据技能id查询 是否存在该技能
        SkillDO from = skillService.queryById(skillReqDTO.getId());
        BizAssert.notNull(from, ExceptionErrorCode.SKILL_NOT_EXISTS);
        try {
            SkillDO skillDO = new SkillDO();
            BeanUtils.copyProperties(skillReqDTO, skillDO);
            skillDO.setUpdatedBy(skillReqDTO.getUpdatedBy());
            if (!StringUtils.isEmpty(skillDO.getSkillName())) {
                // 若存在相同名字的其他技能，则抛出异常
                List<SkillDO> list = skillService.queryByNameAndNoId(skillDO);
                if (list.size() > 0) {
                    throw new BizException(ExceptionErrorCode.SKILL_NAME_ALREADY_EXISTS);
                }
            }
            // 修改技能
            ret = skillService.updateById(skillDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        // 接入glog
        SkillDO to = skillService.queryById(skillReqDTO.getId());
        glogRecorder.recordUpdate(skillReqDTO.getUpdatedBy(),
                "skill", "updateSkill",
                String.valueOf(ret), from, to);
        return ret;
    }

    @Override
    public Boolean deleteSkill(SkillReqDTO skillReqDTO) {
        BizAssert.notNull(skillReqDTO.getId(), ExceptionErrorCode.SKILL_ID_IS_NULL);
        boolean ret;
        // 技能写锁
        String skillLockKey = skillLock(skillReqDTO.getBusinessId());
        try {
            SkillDO skillDO = new SkillDO();
            BeanUtils.copyProperties(skillReqDTO, skillDO);
            // 先根据技能id查询 是否存在该技能
            SkillDO res = skillService.queryById(skillDO.getId());
            BizAssert.notNull(res, ExceptionErrorCode.SKILL_NOT_EXISTS);
            // 删除技能
            ret = skillService.removeById(skillDO.getId());
            // 级联删除
            skillCascadingDelete(skillReqDTO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }

        // 添加glog日志
        glogRecorder.recordDel(skillReqDTO.getUpdatedBy(),
                "skill", "deleteSkill",
                skillReqDTO.toString());
        return ret;
    }

    private void skillCascadingDelete(SkillReqDTO skillReqDTO) {
        BizAssert.notNull(skillReqDTO.getId(), ExceptionErrorCode.SKILL_ID_IS_NULL);
        // 1 查询当前技能的所有技能版本
        List<SkillVersionDO> skillVersionDOS = skillVersionService.listBySkillId(skillReqDTO.getId());
        // 2 删除技能版本下的所有 词库、槽位
        List<Long> skillVids = skillVersionDOS.stream().map(SkillVersionDO::getId).collect(Collectors.toList());
        // 3 查询技能版本下的所有意图
        List<IntentDO> intentDOS = intentService.listBySkillVids(skillVids);
        List<Long> intentIds = intentDOS.stream().map(IntentDO::getId).collect(Collectors.toList());
        try {
            // 批量删除 技能版本、词库、槽位、意图、说法
            skillVersionService.removeByIds(skillVids);
            wordStockService.removeBySkillVids(skillVids);
            slotService.removeBySkillVids(skillVids);
            intentService.removeBySkillVids(skillVids);
            speechRuleService.removeByIntentIds(intentIds);
        } catch (Exception e) {
            log.error("级联删除失败", e);
        }
    }


    @Override
    public Boolean topSkill(SkillReqDTO skillReqDTO) {
        Boolean ret = updateTopStatus(skillReqDTO, IS_TOP);
        return ret;
    }

    @Override
    public Boolean cancelTop(SkillReqDTO skillReqDTO) {
        Boolean ret = updateTopStatus(skillReqDTO, IS_NOT_TOP);
        return ret;
    }

    @Override
    public SkillRespDTO queryById(Long id) {
        SkillRespDTO ret = new SkillRespDTO();
        SkillDO skillDO = skillService.queryById(id);
        BeanUtils.copyProperties(skillDO, ret);
        return ret;
    }

    @Override
    public List<SkillRespDTO> getPublishedSkill() {
        List<SkillDO> skillDOS = skillService.getPublishedSkill();
        List<SkillRespDTO> collect = skillDOS.stream().map(d -> {
            SkillRespDTO dto = new SkillRespDTO();
            BeanUtils.copyProperties(d, dto);
            return dto;
        }).collect(Collectors.toList());
        return collect;
    }

    @Override
    public List<SkillRespDTO> getAllSkill() {
        List<SkillDO> skillDOS = skillService.list();
        List<SkillRespDTO> collect = skillDOS.stream().map(d -> {
            SkillRespDTO dto = new SkillRespDTO();
            BeanUtils.copyProperties(d, dto);
            return dto;
        }).collect(Collectors.toList());
        return collect;
    }

    @Override
    public Map<String, List<String>> getOnlineAndTestSkillIds() {
        Map<String, List<String>> res = new HashMap<>();
        List<String> onlineSkillIds = new ArrayList<>();
        List<String> testSkillIds = new ArrayList<>();
        List<SkillDO> onlineSkillDOS = skillService.onlineSkills();
        if (!CollectionUtils.isEmpty(onlineSkillDOS)) {
            onlineSkillIds = onlineSkillDOS.stream().map(SkillDO::getBusinessId).collect(Collectors.toList());
        }
        List<SkillDO> testSkillDOS = skillService.testSkills();
        if (!CollectionUtils.isEmpty(testSkillDOS)) {
            testSkillIds = testSkillDOS.stream().map(SkillDO::getBusinessId).collect(Collectors.toList());
        }
        res.put(ONLINE, onlineSkillIds);
        res.put(TEST, testSkillIds);
        return res;
    }

    public Boolean updateTopStatus(SkillReqDTO skillReqDTO, Integer topStatus) {
        BizAssert.notNull(skillReqDTO.getId(), ExceptionErrorCode.SKILL_ID_IS_NULL);
        if (redisLockService.isCanNotUpdate(skillReqDTO.getId())) {
            throw new BizException(ExceptionErrorCode.CAN_NOT_UPDATE);
        }
        // 先根据技能id查询 是否存在该技能
        SkillDO res = skillService.queryById(skillReqDTO.getId());
        BizAssert.notNull(res, ExceptionErrorCode.SKILL_NOT_EXISTS);
        SkillDO skillDO = new SkillDO();
        BeanUtils.copyProperties(skillReqDTO, skillDO);
        skillDO.setUpdatedBy(skillReqDTO.getUpdatedBy());
        // 修改技能置顶状态
        skillDO.setTop(topStatus);
        if (IS_TOP.equals(topStatus)) {
            skillDO.setTopTime(new Date());
        }

        boolean ret = skillService.updateById(skillDO);
        return ret;
    }

    /**
     * 加技能锁
     *
     * @param businessId
     */
    private String skillLock(String businessId) {
        String skillUpdateLockKey = RedisKeyBuilder.buildSkillUpdateLockKey(businessId);
        boolean locked = redisLockService.lock(skillUpdateLockKey, NluConstants.NLU_LOCK_EXPIRE_TTS,
                NluConstants.NLU_LOCK_WAIT_TIME_MILLS);
        if (!locked) {
            log.warn("获取技能写锁失败 SkillAppId:{}", businessId);
            throw new BizException(ExceptionErrorCode.SKILL_UPDATE_TRAIN);
        }
        return skillUpdateLockKey;
    }

}
